package felix.rule.service.actors.rule;

import felix.actor.core.*;
import felix.actor.core.msg.TActorMsg;
import felix.actor.core.msg.TMsgType;
import felix.rule.data.msg.EngineMsg;
import felix.rule.data.msg.QueueToRuleEngineMsg;
import felix.rule.data.msg.RuleChain;
import felix.rule.data.msg.RuleEngineException;
import felix.rule.data.plugin.ComponentLifecycleMsg;
import felix.rule.service.actors.ActorSystemContext;
import felix.rule.service.actors.ContextBasedCreator;
import felix.rule.service.actors.TEntityActorId;
import felix.rule.service.actors.chain.RuleChainManagerActor;
import felix.rule.service.actors.chain.RuleChainToRuleChainMsg;
import lombok.extern.slf4j.Slf4j;

import static felix.rule.data.msg.MessageType.*;


@Slf4j
public class RuleActor extends RuleChainManagerActor {

    private RuleActor(ActorSystemContext systemContext, String ruleId) {
        super(systemContext, ruleId);
    }

    @Override
    public void init(TActorCtx ctx) throws TActorException {
        super.init(ctx);
        log.info("[{}] Starting rule actor.", ruleId);
        try {
            initRuleChains();
        } catch (Exception e) {
            log.error("initRuleChains fault", e);
        }
        log.info("[{}] Rule actor started.", ruleId);
    }

    @Override
    public void destroy() {
        log.info("[{}] Stopping rule actor.", ruleId);
    }

    @Override
    protected boolean doProcess(TActorMsg msg) {
        TMsgType msgType = msg.getMsgType();
        if (QUEUE_TO_RULE_ENGINE_MSG.equals(msgType)) {
            onQueueToRuleEngineMsg((QueueToRuleEngineMsg) msg);
        } else if (RULE_CHAIN_TO_RULE_CHAIN_MSG.equals(msgType)) {
            onRuleChainMsg((RuleChainToRuleChainMsg) msg);
        } else if (COMPONENT_LIFE_CYCLE_MSG.equals(msgType)) {
            onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
        } else {
            return false;
        }
        return true;
    }

    private void onRuleChainMsg(RuleChainToRuleChainMsg msg) {
        getOrCreateActor(msg.getRuleChainId()).tell(msg);
    }

    private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
        //ruleChainId
        TActorRef target = getEntityActorRef(msg.getEntityId());
        if (target != null) {
            if (msg.getType().equals(RuleActor.class.getSimpleName())) {
                RuleChain ruleChain = systemContext.getRuleChainService().
                        findRuleChainById(msg.getEntityId());
                visit(ruleChain, target);
            }
            target.tellWithHighPriority(msg);
        } else {
            log.debug("[{}] Invalid component lifecycle msg: {}", ruleId, msg);
        }
    }

    private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) {
        EngineMsg engineMsg = msg.getEngineMsg();
        if (engineMsg.getRuleChainId() == null) {
            if (getRootChainActor() != null) {
                getRootChainActor().tell(msg);
            } else {
                engineMsg.getCallback().onFailure(new RuleEngineException("No Root Rule Chain available!"));
                log.info("[{}] No Root Chain: {}", ruleId, msg);
            }
        } else {
            try {
                ctx.tell(new TEntityActorId(engineMsg.getRuleChainId(), RuleActor.class.getSimpleName()), msg);
            } catch (TActorNotRegisteredException ex) {
                log.trace("Received message for non-existing rule chain: [{}]", engineMsg.getRuleChainId());
                engineMsg.getCallback().onSuccess();
            }
        }
    }

//    private void onRuleChainMsg(RuleChainAwareMsg msg) {
//        getOrCreateActor(msg.getRuleChainId()).tell(msg);
//    }

    public static class ActorCreator extends ContextBasedCreator {

        private final String ruleId;

        public ActorCreator(ActorSystemContext context, String ruleId) {
            super(context);
            this.ruleId = ruleId;
        }

        @Override
        public TActorId createActorId() {
            return new TEntityActorId(ruleId, RuleActor.class.getSimpleName());
        }

        @Override
        public TActor createActor() {
            return new RuleActor(context, ruleId);
        }
    }

}
